﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using LINQPad.OpenAccess.Helpers;
using Telerik.OpenAccess;
using Telerik.OpenAccess.Metadata;

namespace LINQPad.OpenAccess.Metadata.Reflection.Locators
{
    /// <summary>
    /// <see cref="IMetadataSourceLocator"/> for metadata sources that are based on metadata attributes.
    /// </summary>
    public class AttributesMetadataSourceLocator : IMetadataSourceLocator
    {
        #region Private

        #region - Vars

        [DebuggerBrowsable( DebuggerBrowsableState.Never )]
        private readonly IReflectionService _reflectionService;

        #endregion

        #region - Functions

        /// <summary>
        /// Assembles the metadata source info.
        /// </summary>
        /// <param name="contextType">Type of the context.</param>
        /// <returns>The metadata source info.</returns>
        private IMetadataSourceInfoWithSource AssembleMetadataSourceInfoWithSource( Type contextType )
        {
            var metadataSource = AttributesMetadataSource.FromContext( contextType );

            return new MetadataSourceInfoWithSource( contextType.Name, contextType.Namespace, metadataSource );
        }
        /// <summary>
        /// Determines whether the specified member has at least one metadata attribute defined.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>
        ///   <c>true</c> if the specified member has at least one metadata attribute defined; otherwise, <c>false</c>.
        /// </returns>
        private bool EntitySetPropertyHasMetadataAttributesDefined( EntitySetInfo entitySetInfo )
        {
            if ( entitySetInfo == null )
                return false;

            return ReflectionHelper.IsMarkedWithAttribute<MetadataAttribute>( entitySetInfo.EntityType )
                    || ReflectionHelper.IsMarkedWithAttribute<MetadataAttribute>( entitySetInfo.EntitySetType );
        }
        /// <summary>
        /// Determines whether the context type has at least one metadata attribute defined.
        /// </summary>
        /// <param name="contextType">Type of the context.</param>
        /// <returns>
        ///   <c>true</c> if the context type has at least one metadata attribute defined; otherwise, <c>false</c>.
        /// </returns>
        private bool HasMetadataAttributesDefined( Type contextType )
        {
            if ( contextType == null )
                return false;

            if ( this._reflectionService
                    .GetEntitySetsFromContext( contextType )
                    .Any( EntitySetPropertyHasMetadataAttributesDefined ) )
                return true;

            if ( contextType.GetMethods( )
                            .Any( ReflectionHelper.IsMarkedWithAttribute<MetadataAttribute> ) )
                return true;

            return false;
        }

        #endregion

        #endregion

        #region Public

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AttributesMetadataSourceLocator" /> class.
        /// </summary>
        /// <param name="reflectionService">The reflection service.</param>
        public AttributesMetadataSourceLocator( IReflectionService reflectionService )
        {
            Guard.ArgumentNotNull( reflectionService, "reflectionService" );

            this._reflectionService = reflectionService;
        }

        #endregion

        #endregion

        #region IMetadataSourceLocator Members

        /// <summary>
        /// Gets the metadata source infos from the specified assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>
        /// The metadata source infos.
        /// </returns>
        public IEnumerable<IMetadataSourceInfoWithSource> GetFromAssembly( Assembly assembly )
        {
            Guard.ArgumentNotNull( assembly, "assembly" );

            var metadataSources = this._reflectionService
                                      .GetContextTypesFromAssembly( assembly )
                                      .Where( HasMetadataAttributesDefined )
                                      .Select( AssembleMetadataSourceInfoWithSource );

            return metadataSources;
        }

        #endregion
    }
}